<html lang="en-US">

<head>
    
<title>为什么 Go 的泛型一拖再拖？ - 花落雨忧</title>

<meta property="og:title" content="为什么 Go 的泛型一拖再拖？ - 花落雨忧">



    



    
    <meta property="description" content="大家好，我是煎鱼。前段时间 Go 语言的泛型讨论频频出现在各微信群，且又冲上了国内外各大文章的 “头条”：
信息汇总来看，Go 泛型这几年会出，但大体来讲现在 Go 泛型又又又推迟了，好家伙。我最早了解到时是考虑 Go1.16 释出，后面又推到了 Go1.17，接着现在又延期到了 Go1.18 了（2021 年底）。
看到了信息的表象后，再想想为什么泛型 “这件事情” 突然醒目起来了，其原因之一是 &amp;hellip;">
    <meta property="og:description" content="大家好，我是煎鱼。前段时间 Go 语言的泛型讨论频频出现在各微信群，且又冲上了国内外各大文章的 “头条”：
信息汇总来看，Go 泛型这几年会出，但大体来讲现在 Go 泛型又又又推迟了，好家伙。我最早了解到时是考虑 Go1.16 释出，后面又推到了 Go1.17，接着现在又延期到了 Go1.18 了（2021 年底）。
看到了信息的表象后，再想想为什么泛型 “这件事情” 突然醒目起来了，其原因之一是 &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.site/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.site/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.site/js/jquery.min.js"></script>




<link href="https://www.lican.site/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>
        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/go/go16-preview/">为什么 Go 的泛型一拖再拖？</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2020/11/12</a>
                                            
                                            
                                            
                                            
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p>大家好，我是煎鱼。前段时间 Go 语言的泛型讨论频频出现在各微信群，且又冲上了国内外各大文章的 “头条”：</p>
<p><img src="https://image.eddycjy.com/c9c48e9479c7036f7d5a33b6ab49e855.jpg" alt="来自 p 神公众号的截图"></p>
<p>信息汇总来看，Go 泛型这几年会出，但大体来讲现在 Go 泛型又又又推迟了，好家伙。我最早了解到时是考虑 Go1.16 释出，后面又推到了 Go1.17，接着现在又延期到了 Go1.18 了（2021 年底）。</p>
<p>看到了信息的表象后，再想想为什么泛型 “这件事情” 突然醒目起来了，其原因之一是由官方 <a href="https://blog.golang.org/11years">Go，11 岁</a> 的博文所引爆的。</p>
<p>同时近日举办的 GopherCon2020 大会，Robert Griesemer 分享的 Typing [Generic] Go。更正式的让 Go 泛型更面向了大众，也侧面的说明官方认为其已经到达了一个新的阶段了，进入最终实现阶段。</p>
<p>事不宜迟，既然官方都已经摩拳擦掌了，我们的学习之路也得跟上，因此本文将会介绍 Go 泛型现在的情况，并通过在介绍过程中不断思考最后得出一个为什么。</p>
<h2 id="什么是泛型">什么是泛型</h2>
<p>泛型程序设计（generic programming）是程序设计语言的一种风格或范式。泛型允许程序员在强类型语言中编写代码时，使用一些以后才确定的类型，其在真正实例化时才会为这些参数指确定类型。另外各语言和其编译器、运行环境对泛型的支持均不一样，因此需要针对来辩证。</p>
<p>简单来讲，泛型就是参数化多态。其可根据实参类型生成不同的版本，支持任意数量的调用：</p>
<pre><code>func F(a, b T) T{ return a+b }

// T 为 int
F(1, 2)

// T 为 string
F(&quot;1&quot;, &quot;2&quot;)
</code></pre><p>在编译时期编译器便确定其 T 的入参类型。这也是 Go 泛型实现的要求之一 “编译时类型安全”。</p>
<h2 id="为什么需要泛型">为什么需要泛型</h2>
<p>这时候可能会有人说，没有泛型也可以啊&hellip;感觉写业务代码没什么影响，与其搞泛型不如搞好 errors（具体新消息可参见：重磅：Go errors 将不会有任何进一步的改进计划）。</p>
<p>但泛型是有其所需的场景，最常见的是像基础库在处理获取配置中心数据时，就要处理类型，时常遇到下述场景：</p>
<p><img src="https://image.eddycjy.com/4d630c956a58bd4b88a4a6e0cddbb845.gif" alt="image"></p>
<p>如果使用接口（interface）类型来做，也得 <code>switch.(type)</code> 枚举出所有的基础类型。这显然并不合理，也没法做太复杂的逻辑，而且所支持的类型还泄露。</p>
<p>另外同时单从语言层面来讲，泛型支持是一个必然事件了，因为泛型的存在对解决特定领域的问题存在一定的意义。</p>
<h2 id="接口和泛型有什么区别">接口和泛型有什么区别</h2>
<p>在上面我们有提到接口（interface）类型，这时候就出现了泛型的第二个经典问题。那就是 “接口和泛型有什么区别？”，为什么不用接口来实现 “泛型”：</p>
<pre><code>type T interface { ... }
func F(a, b T) T { return a+b }
</code></pre><p>也像这么一回事，但在这里存在一个致命的缺陷。那就是接口的入参和出参均可以在运行时表现为不同的类型：</p>
<pre><code>F(&quot;煎鱼&quot;, 233)
</code></pre><p>要做好，还得依靠内部去对参数进行断言，否则作为 string 类型的煎鱼又如何和 int 类型的 233 相加呢，那是必然报错的。</p>
<p>而反过来看真 “泛型” 的实际使用，编译器会保证泛型函数的入参和出参必须为同一类型，有强制性的检验：</p>
<pre><code>// 报错：type checking failed for main
F(&quot;煎鱼&quot;, 233)

// 必须为同一类型，才能正常运行
F(666, 233)
</code></pre><p>两者存在本质上的区别，泛型会更安全，能够保证编译早期就发现错误，而不是等到运行时（并且可能会存在隐性的 BUG）。</p>
<p>总体来讲，泛型相较接口有如下优点：</p>
<ul>
<li>
<p>更安全：编译早期就能发现错误。</p>
</li>
<li>
<p>性能好：静态类型。</p>
</li>
</ul>
<h2 id="过去为什么那么久都没有泛型">过去：为什么那么久都没有泛型</h2>
<p>前几段在社区的微信群看到一位小伙伴吐槽 “Go 语言居然没有泛型？”，变相来看，可能其会认为 ”Go 都已经 11 岁了，2020 年了居然还没有泛型？”。</p>
<p>这显然是不对的，因为泛型本质上并不是绝对的必需品，更不是 Go 语言的早期目标，因此在过往的发展阶段没有过多重视这一点，而是把精力放在了其他 feature 上。</p>
<p>另外 Go 语言在以往其实进行过大量的泛型 proposal 试验，基本时间线（via @changkun）如下：</p>
<table>
<thead>
<tr>
<th>简述</th>
<th>时间</th>
<th>作者</th>
</tr>
</thead>
<tbody>
<tr>
<td>[Type Functions]</td>
<td>2010年</td>
<td>Ian Lance Taylor</td>
</tr>
<tr>
<td>Generalized Types</td>
<td>2011年</td>
<td>Ian Lance Taylor</td>
</tr>
<tr>
<td>Generalized Types v2</td>
<td>2013年</td>
<td>Ian Lance Taylor</td>
</tr>
<tr>
<td>Type Parameters</td>
<td>2013年</td>
<td>Ian Lance Taylor</td>
</tr>
<tr>
<td>go:generate</td>
<td>2014年</td>
<td>Rob Pike</td>
</tr>
<tr>
<td>First Class Types</td>
<td>2015年</td>
<td>Bryan C.Mills</td>
</tr>
<tr>
<td>Contracts</td>
<td>2018年</td>
<td>Ian Lance Taylor, Robert Griesemer</td>
</tr>
<tr>
<td>Contracts</td>
<td>2019年</td>
<td>Ian Lance Taylor, Robert Griesemer</td>
</tr>
<tr>
<td>Redundancy in Contracts(2019)&lsquo;s Design</td>
<td>2019年</td>
<td>Ian Lance Taylor, Robert Griesemer</td>
</tr>
<tr>
<td>Constrained Type Parameters(2020, v1)</td>
<td>2020年</td>
<td>Ian Lance Taylor, Robert Griesemer</td>
</tr>
<tr>
<td>Constrained Type Parameters(2020, v2)</td>
<td>2020年</td>
<td>Ian Lance Taylor, Robert Griesemer</td>
</tr>
<tr>
<td>Constrained Type Parameters(2020, v3)</td>
<td>2020年</td>
<td>Ian Lance Taylor, Robert Griesemer</td>
</tr>
</tbody>
</table>
<p>虽然偶有中断，但仔细一看，2010 年就尝试过，现在 2020 年了，也是很励志了，显然官方也是在寻路和尝试的过程中，但一直没有找到相较好的方案，争端过多了。</p>
<h2 id="现在go-泛型">现在：Go 泛型</h2>
<p>泛型尝鲜的方式有两种方式。线上 Ian Lance Taylor 提供了一个在线编译的 <a href="https://go2goplay.golang.org/">go2go</a>：</p>
<p><img src="https://image.eddycjy.com/0609310f0a775b57fe017f56c1e50195.jpg" alt="image"></p>
<p>另外一种是线下，也就在本地安装 Go 的特定分支版本：</p>
<pre><code>$ git clone https://github.com/golang/go
$ git checkout dev.go2go
$ cd src &amp;&amp; ./all.bash
</code></pre><p>不过这种本地安装的方法会耗时比较久，初步尝试的话建议使用 go2go 就可以了。而在尝鲜时，可以看到在代码块中声明了一个 <code>Print</code> 方法，其函数签名主体分为三部分：</p>
<p><img src="https://image.eddycjy.com/5fc715bb226563645dfc6bb4da210c84.jpg" alt="image"></p>
<p>咋一看，变量 T 的这个关键字 <code>any</code> 是什么？早期泛型你可能有听说合约（Contract），难道这就是合约。其实严格意义上来讲并不是，因为为了更一步简化语法，合约在 2020.06.07 已经正式移除。</p>
<p>其已改头换面，现在只需要写参数化的 interface。而上述的 <code>any</code> 关键字是一个预定义的类型约束，声明后将允许任何类型用作类型实参，并且允许函数使用用于任何类型的操作。</p>
<p>从语法分析的角度来讲，<code>Print</code> 方法一共包含了如下属性（从左到右）：</p>
<ul>
<li>
<p>type list：声明了入参的类型列表为一个 <code>T</code> 变量，其可以传任意类型的参数。</p>
</li>
<li>
<p>parameter list：声明了入参的参数列表为 <code>T</code> 变量的切片，且形参为 <code>s</code>。</p>
</li>
<li>
<p>return type list：声明了函数的返回参数列表。</p>
</li>
</ul>
<p>上述函数签名便是一个 Go 泛型的基本样子，由于本文并不是 CRUD 泛型，便不展开案例，若大家有兴趣可以详细阅读提案：<a href="https://github.com/golang/proposal/blob/master/design/go2draft-type-parameters.md">Type Parameters - Draft Design</a>。</p>
<h2 id="泛型的战争">泛型的战争</h2>
<h3 id="为什么不用尖括号">为什么不用尖括号</h3>
<p>在社区中很多同学在讨论的一个问题，那就是 “为什么 Go 泛型不像 C++ 和 Java 那样使用尖括号？，也出现了 “Go 一直标榜业界工程实践类的榜样，为什么就是不用尖括号” 的言论？</p>
<p>思考问题我们不只看表面，官方说不行，那么我们可以倒推来看，看看 Go 语言就用尖括号：</p>
<pre><code>func print&lt;type T&gt;(list []T) {

print&lt;int&gt;(numbers)
print&lt;string&gt;(strings)
print&lt;float64&gt;(floats)
</code></pre><p>普通的函数声明看上去似乎结构清晰，没有什么大问题的。接着往下看：</p>
<pre><code>a := w &lt; x
b := y &gt; (z)
</code></pre><p>我们继续把代码演进一下，简洁一点：</p>
<pre><code>a, b := w &lt; x, y &gt; (z)
</code></pre><p>这时候就犯难了，不仅编译器难以解析，人也很难判别，到底指的是：</p>
<pre><code>a := w &lt; x
b := y &gt; (z)
</code></pre><p>又或是：</p>
<pre><code>a, b := w&lt;x, y&gt;(z)
</code></pre><p>从上述代码来看，使用尖括号难以分别，因为没有类型信息，就无法确定赋值的右侧是一对表达式 <code>w &lt; x和y &gt; (z)</code>，还是返回两个结果值 <code>w&lt;x, y&gt;(z)</code> 的泛型函数实例化和调用，其存在歧义。</p>
<p>要解决还要引入新的约束，会破坏 Go1 的兼容性承诺，这显然是不合理的。</p>
<h3 id="为什么不用括号">为什么不用括号</h3>
<p>其实最早 Go 泛型的版本是使用了括号的模式，虽然能用，但是用括号会引入新的解析歧义。例如：</p>
<pre><code>var f func(x(T))
</code></pre><p>从语法上来讲，你无法识别他是未命名参数的 <code>x(T)</code> 函数，还是类型名为参数的 <code>(T)</code> 函数。同时 Go 语言还存在强制类型转换这一语法，假设代码是 <code>[]T(v1)</code> 和 <code>[]T(v2){}</code> ，那么你在开括号处，就无法得知其是否代表类型转换。</p>
<p>更甚至在函数的完整声明上，我们都会感到困惑：</p>
<pre><code>func F(T any)(v T)(r1, r2 T)
</code></pre><p>函数入参、泛型、返回值声明均都是括号，造成了语义不清，这显然也是不合理的。</p>
<h3 id="为什么不用书名号">为什么不用书名号（«»）</h3>
<p>想的美，并不想使用非 ASCII，未来更没打算支持。</p>
<h2 id="总结">总结</h2>
<p>在本文中我们从多个维度介绍了 Go 泛型的相关内容，既了解到了上段时间 Go 泛型再度火爆的信息来源是什么。也知道了 Go 泛型是什么，与接口的区别。</p>
<p>同时我们还针对业界常见的一些疑问，例如接口和泛型的区别，泛型的历史，泛型的尖括号/括号/书名号之争进行了解释和说明。</p>
<p>最后我们回答一下最开始的疑问，”为什么 Go 的泛型一拖再拖“，主要如下：</p>
<ul>
<li>
<p>Go 语言的早期目标（工作重点）并不是泛型。</p>
</li>
<li>
<p>Go 语言在 2010-2020 年都有间断在做 Go 泛型的 proposal，但总是 ”失败“，在不断地吸收经验。</p>
</li>
<li>
<p>Go 语言社区的意见反馈是真的多，单用什么符号表示泛型，不想要泛型都争论不休。</p>
</li>
<li>
<p>Go 语言的泛型现在还不成熟，很多​细节其实并没有支持好。</p>
</li>
</ul>
<p>很显然，在保证 Go1 向后兼容性的同时，Go 官方也不想直接妥协出一个随便的方案，因此总是不断地在改进。随着 Go 语言的不断应用，泛型也和 errors 一样被推上风头浪尖。</p>
<h2 id="到底拖到什么时候">到底拖到什么时候</h2>
<p>那 Go 泛型到底什么时候出呢？</p>
<p>前段时间也向欧神（@changkun）了解到在 GopherCon 2020 Go Team AMA，russ cox 有聊到相关问题，表示在明年年底要能有生产环境的试用版上线，这是一个管理目标。</p>
<p>但具体真正的时间线肯定是要看泛型的实现者：robert 和 keith，可以多多关注他们，就能拿到一手信息，且可以确定的是 Go 泛型明年二月之前是不会有生产可用的试用版。</p>
<p><strong>灵魂拷问：你对 Go 语言的泛型又有什么想法和意见呢，一起留言讨论吧。</strong></p>
<h2 id="推荐阅读">推荐阅读</h2>
<ul>
<li><a href="https://talkgo.org/t/topic/99">欧神：第 80 期 2020-03-18 带你提前玩 Go 2 新特性：泛型</a></li>
<li><a href="https://github.com/golang/proposal/blob/master/design/go2draft-type-parameters.md">提案：Type Parameters - Draft Design</a></li>
</ul>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/go11/">Go 语言今年 11 岁，何去何从，现状到底如何？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/reflect/">解密 Go 语言之反射 reflect</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/go-bootstrap/">Go 应用程序是怎么运行起来的？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go-programming-tour-book/">新书《Go语言编程之旅：一起用Go做项目》出版啦！</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/why-container-memory-exceed2/">为什么容器内存占用居高不下，频频 OOM（续）</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.site/posts/go/go11/"
                                                    rel="prev">Go 语言今年 11 岁，何去何从，现状到底如何？</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.site/posts/go/gophercon2020-errors/">重磅：Go errors 将不会有任何进一步的改进计划</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.site/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.vip All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>